package gable

import (
	"bytes"
	"encoding/binary"
	"fmt"
	"log"
	"strings"
	"unsafe"

	"extractor/gable-reader/gable/stream"
)

const (
	WordDocumentStreamName = "WordDocument"
	TableStream0Name       = "0Table"
	TableStream1Name       = "1Table"
)

type FileInformationBlockHeader struct {
	Base      FibBase
	Csw       uint16
	FibRgW    [28]byte
	Cslw      uint16
	FibRgLw   FibRgLw97
	CbRgFcLcb uint16
}

type FibBase struct {
	WIdent   int16
	NFib     int16
	_        int16
	Lid      int16
	PnNext   int16
	StateBit int16
	_        [20]byte
}

type FibRgLw97 struct {
	CbMac      int32
	_          [8]byte
	CcpText    int32
	CcpFtn     int32
	CcpHdd     int32
	_          int32
	CcpAtn     int32
	CcpEdn     int32
	CcpTxbx    int32
	CcpHdrTxbx int32
	_          [44]byte
}

type Pcd struct {
	StateBits uint16
	Fc        uint32
	Prm       [2]byte
}

type PLC struct {
	ACP []int32
}

type WordDocument struct {
	CompoundDoc        *CompoundDocument
	WordDocumentStream *stream.Stream
	TableStream        *stream.Stream
}

func (doc *WordDocument) initWordDocumentStream() error {
	comp := doc.CompoundDoc
	wordDocumentStream, err := comp.GetStream(WordDocumentStreamName)
	doc.WordDocumentStream = &wordDocumentStream
	return err
}

func (doc *WordDocument) initTableStream() error {
	comp := doc.CompoundDoc
	fWhichTableStream := (*doc.WordDocumentStream).ReadRange(11, 12)[0] >> 6 & 1
	var tableStream stream.Stream
	var err error = nil
	if fWhichTableStream == 0 {
		tableStream, err = comp.GetStream(TableStream0Name)
	} else {
		tableStream, err = comp.GetStream(TableStream1Name)
	}
	if tableStream != nil {
		doc.TableStream = &tableStream
	}
	return err
}

func (doc *WordDocument) initStyleProperties() {
	wordDocumentStream := *doc.WordDocumentStream
	fcStshf := binary.LittleEndian.Uint32(wordDocumentStream.ReadRange(162, 166))
	lcbPlcfBtePapx := binary.LittleEndian.Uint32(wordDocumentStream.ReadRange(166, 170))

	tableStream := *doc.TableStream

	pt := fcStshf
	cbStshi := binary.LittleEndian.Uint16(tableStream.ReadRange(pt, pt+2))
	pt += 2 + uint32(cbStshi)
	lastPt := fcStshf + lcbPlcfBtePapx

	for pt < lastPt {
		cbStd := uint32(binary.LittleEndian.Uint16(tableStream.ReadRange(pt, pt+2)))
		pt += 2
		buff := tableStream.ReadRange(pt, pt+cbStd)
		pt += cbStd
		fmt.Printf("%X\n", binary.LittleEndian.Uint16(buff[:2]))
	}
	// pt := fcStshf
	// cbStshi := binary.LittleEndian.Uint16(tableStream.ReadRange(pt, pt+2))
	// pt += 2
	// pt += uint32(cbStshi)
	// for pt < fcStshf+lcbPlcfBtePapx {
	// 	cbStd := uint32(binary.LittleEndian.Uint16(tableStream.ReadRange(pt, pt+2)))
	// 	cbStd += 2
	// }
}

func (doc *WordDocument) retrieveTextStream(start, stop uint32) {
	wordDocumentStream := *doc.WordDocumentStream
	fcClx := binary.LittleEndian.Uint32(wordDocumentStream.ReadRange(418, 422))
	fcPlcfBtePapx := binary.LittleEndian.Uint32(wordDocumentStream.ReadRange(258, 262))
	lcbPlcfBtePapx := binary.LittleEndian.Uint32(wordDocumentStream.ReadRange(262, 266))

	tableStream := *doc.TableStream
	pos := fcClx
	buf := make([]byte, 3)
	augment := uint32(0)
	buf[0] = 1
	for buf[0] == 1 {
		pos += augment
		buf = tableStream.ReadRange(pos, pos+3)
		augment = uint32(binary.LittleEndian.Uint16(buf[1:])) + 3
	}
	if buf[0] != 2 {
		return
	}

	lcb := binary.LittleEndian.Uint32(tableStream.ReadRange(pos+1, pos+5))
	pos += 5

	rgCount := (lcb - 4) / 12

	sep := rgCount*4 + 4

	aCpBuf := tableStream.ReadRange(pos, pos+sep)
	aPcdBuf := tableStream.ReadRange(pos+sep, pos+lcb)
	aCp := (*(*[]uint32)(unsafe.Pointer(&aCpBuf)))[1 : rgCount+1]
	aPcd := (*(*[]uint64)(unsafe.Pointer(&aPcdBuf)))[:rgCount]

	lastCp := uint32(0)
	for idx, pcd := range aPcd {
		fc := uint32((pcd & 0xFFFFFFFF0000) >> 16)
		prm := uint16(pcd & 0x000000000000FFFF)
		fmt.Printf("prm 0X%X in pcd 0X%X\n", prm, pcd)
		isCompress := fc & 0x40000000
		fc = fc & 0x3FFFFFFF
		cp := aCp[idx] - lastCp
		lastCp = aCp[idx]
		if isCompress == 0 {
			s, _ := UnicodeDecoder.Bytes(wordDocumentStream.ReadRange(fc, fc+cp))
			ss := string(s)

			sss := bytes.Split(s, []byte("\x07"))
			for _, sssItem := range sss {
				sssItemStr := string(sssItem)
				fmt.Print(sssItemStr)
			}

			ss = strings.Replace(ss, "\u000D", "\n", -1)
			print(string(ss))
			fmt.Printf("%T\n", ss)
		} else {
			fc /= 2
			s := (string)(wordDocumentStream.ReadRange(fc, fc+cp))
			ss := strings.Replace(s, "\u000D", "\n", -1)
			print(&ss)
			fmt.Printf("%T\n", ss)
		}
	}

	println()

	plcCount := (lcbPlcfBtePapx - 4) / 8
	sep = plcCount*4 + 4
	aFcBuf := tableStream.ReadRange(fcPlcfBtePapx, fcPlcfBtePapx+sep)
	aPnBtePapxBuf := tableStream.ReadRange(fcPlcfBtePapx+sep, fcPlcfBtePapx+lcbPlcfBtePapx)
	aFc := (*(*[]uint32)(unsafe.Pointer(&aFcBuf)))[0 : plcCount+1]
	aPnBtePapx := (*(*[]uint32)(unsafe.Pointer(&aPnBtePapxBuf)))[:plcCount]

	// var beginOff uint32 = 0
	// var endOff uint32 = 0

	for _, pnBtePapx := range aPnBtePapx {
		papxFkp := wordDocumentStream.ReadRange(pnBtePapx*512, pnBtePapx*512+512)
		cpara := uint32(papxFkp[511])

		for idx := uint32(0); idx < cpara; idx++ {
			bOffset := uint32(papxFkp[cpara*4+4+idx*13])
			if bOffset == 0 {
				break
			}
			papxInFkpCb := uint32(papxFkp[bOffset*2])
			var grpPrlAndIstd []byte
			if papxInFkpCb == 0 {
				papxInFkpCb = uint32(papxFkp[bOffset*2+1])
				grpPrlAndIstd = papxFkp[bOffset*2+2 : bOffset*2+2+papxInFkpCb*2]
			} else {
				grpPrlAndIstd = papxFkp[bOffset*2+1 : bOffset*2+papxInFkpCb*2]
			}

			istd := binary.LittleEndian.Uint16(grpPrlAndIstd[:2])
			if istd != 0 {
				fmt.Printf("0X%X\n", istd)
			}
			pt := 2
			for pt < len(grpPrlAndIstd) {
				sprm := binary.LittleEndian.Uint16(grpPrlAndIstd[pt : pt+2])
				pt += 2
				if sprm == 0xD608 {
					ku := binary.LittleEndian.Uint16(grpPrlAndIstd[pt : pt+2])
					fmt.Println(ku)
				}
				// fmt.Printf("sprm: 0X%X\n", sprm)
				spra := sprm >> 13
				sizeOperand := 2
				switch spra {
				case 0:
					sizeOperand = 1
				case 1:
					sizeOperand = 1
				case 3:
					sizeOperand = 4
				case 7:
					sizeOperand = 3
				case 6:
					if sprm == 0xD608 {
						sizeOperand = int(binary.LittleEndian.Uint16(grpPrlAndIstd[pt : pt+2]))
						pt += 2
					} else if sprm == 0xC615 {
						sizeOperand = int(grpPrlAndIstd[pt])
						pt++
						if sizeOperand == 255 {
							PChgTabsDelCloseCTabs := int(grpPrlAndIstd[pt])
							PChgTabsAddCTabs := int(grpPrlAndIstd[pt+PChgTabsDelCloseCTabs*4])
							sizeOperand = 4*PChgTabsDelCloseCTabs + 3*PChgTabsAddCTabs
						}
					} else {
						sizeOperand = int(grpPrlAndIstd[pt])
						pt++
					}

				}

				switch sprm & 0x1FF {
				// case 0x16:
				// 	if grpPrlAndIstd[pt] == 1 {

				// 		rgfc := binary.LittleEndian.Uint32(papxFkp[idx*4 : idx*4+4])
				// 		nextRgfc := binary.LittleEndian.Uint32(papxFkp[idx*4+4 : idx*4+8])

				// 		if beginOff == 0 {
				// 			beginOff = rgfc
				// 			endOff = nextRgfc
				// 		} else if endOff == rgfc {
				// 			endOff = nextRgfc
				// 		} else {
				// 			fmt.Printf("[%d,%d)\n", beginOff, endOff)
				// 			beginOff = rgfc
				// 			endOff = nextRgfc
				// 		}
				// 	}
				case 0x17:
					if grpPrlAndIstd[pt] == 1 {
						rgfc := binary.LittleEndian.Uint32(papxFkp[idx*4 : idx*4+4])
						fmt.Println("end row mark pos: ", rgfc)
					}
				case 0xD608:
					numberOfColumns := grpPrlAndIstd[pt]
					fmt.Printf("Number Of Columns: %d", numberOfColumns)
				}

				pt += sizeOperand
			}
		}
	}

	println(aPnBtePapx)
	println(aFc)
}

func LoadWordDocument(path string) {
	comp := LoadCompoundDocument(path)
	doc := WordDocument{
		CompoundDoc: comp,
	}
	err := doc.initWordDocumentStream()
	if err != nil {
		log.Fatal(err)
	}

	err = doc.initTableStream()
	if err != nil {
		log.Fatal(err)
	}

	// doc.initStyleProperties()
	doc.retrieveTextStream(0, 1000)
}
